/* Copyright(C) 2006-2008 Dave Sexton  *
 * http://www.codeplex.com/aip         *
 * http://www.codeplex.com/aip/license *
 ***************************************/
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
using System.Configuration.Provider;

namespace DaveSexton.Web.Controls
{
	/// <summary>
	/// An implementation of <see cref="AutoInputProtectionFilterProvider"/> that renders an overlay using a 
	/// <see cref="HatchBrush"/>.  This class cannot be inherited.
	/// </summary>
	/// <remarks>
	/// <para>
	/// The crosshatch is rendered in a single <see cref="Color"/> that is sampled at random from the list of 
	/// <see cref="Colors"/>.  If <see cref="RandomStyle"/> is <see langword="true"/> then a random <see cref="HatchStyle"/>
	/// is used; otherwise, the specified <see cref="Style"/> is used.
	/// </para>
	/// <para>
	/// You can use this filter to increase the randomization of the images that AIP generates and to change the appearance of images 
	/// to match the aesthetics of the web site.
	/// </para>
	/// </remarks>
	/// <include file='examples.xml' path='//example[@id="Crosshatch"][@type="images"]'/>
	/// <include file='examples.xml' path='//example[@id="Crosshatch"][@type="config"]'/>
	/// <seealso cref="AutoInputProtection.FilterProviders"/>
	/// <seealso cref="Configuration.AutoInputProtectionSection"/>
	/// <seealso cref="AutoInputProtectionTextProvider"/>
	/// <seealso cref="AutoInputProtectionImageProvider"/>
	public sealed class CrosshatchAutoInputProtectionFilterProvider : AutoInputProtectionFilterProvider
	{
		#region Public Properties
		/// <summary>
		/// Gets a list of <see cref="Color">Colors</see> from which one will be chosen at random and used to draw the 
		/// lines of the crosshatch.
		/// </summary>
		/// <remarks>
		/// At least one <see cref="Color"/> must be specified or else the <see cref="PostProcess"/> method will throw 
		/// an <see cref="InvalidOperationException"/>.
		/// </remarks>
		/// <value>List of <see cref="Color">Colors</see> from which one will be chosen at random and used to draw the 
		/// lines of the crosshatch.</value>
		public IList<Color> Colors
		{
			get
			{
				return colors;
			}
		}

		/// <summary>
		/// Gets or sets the opacity of the lines as a floating point number between 0 and 1, inclusive, where 0 
		/// is absolute transparency and 1 is absolute opacity.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">The specified value is less than zero or greater than one.</exception>
		/// <value>Opacity of the lines as a floating point number between 0 and 1, inclusive.</value>
		public float Opacity
		{
			get
			{
				return opacity;
			}
			set
			{
				if (value < 0 || value > 1)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueBetween0And1Inclusive);

				opacity = value;
			}
		}

		/// <summary>
		/// Gets or sets whether a random <see cref="HatchStyle"/> will be chosen.
		/// </summary>
		/// <remarks>
		/// When this property is set to a value of <see langword="true"/> the <see cref="Style"/> property is ignored.
		/// </remarks>
		/// <seealso cref="Style"/>
		/// <value>Indicates whether a random <see cref="HatchStyle"/> will be chosen.</value>
		public bool RandomStyle
		{
			get
			{
				return randomStyle;
			}
			set
			{
				randomStyle = value;
			}
		}

		/// <summary>
		/// Gets or sets the <see cref="HatchStyle"/> to be used when drawing the crosshatch.
		/// </summary>
		/// <remarks>
		/// This property is ignored if <see cref="RandomStyle"/> is <see langword="true"/>.
		/// </remarks>
		/// <exception cref="System.ComponentModel.InvalidEnumArgumentException">The specified value is not a valid 
		/// <see cref="HatchStyle"/> constant.</exception>
		/// <seealso cref="RandomStyle"/>
		/// <value>The <see cref="HatchStyle"/> to be used when drawing the crosshatch.</value>
		public HatchStyle Style
		{
			get
			{
				return style;
			}
			set
			{
				if (!Enum.IsDefined(typeof(HatchStyle), value))
					throw new System.ComponentModel.InvalidEnumArgumentException("value", (int) value, typeof(HatchStyle));

				style = value;
			}
		}

		/// <summary>
		/// Gets whether the filter supports post-processing.
		/// </summary>
		/// <seealso cref="PostProcess"/>
		/// <value>The base implementation always returns <see langword="true"/> to indicate that post-processing is 
		/// supported by this filter.</value>
		public override bool CanPostProcess
		{
			get { return true; }
		}

		/// <summary>
		/// Gets whether the filter supports preprocessing.
		/// </summary>
		/// <seealso cref="PreProcess"/>
		/// <value>The base implementation always returns <see langword="false"/> to indicate that preprocessing is 
		/// not supported by this filter.</value>
		public override bool CanPreProcess
		{
			get { return false; }
		}
		#endregion

		#region Private / Protected
		private readonly List<Color> colors;
		private float opacity;
		private bool randomStyle;
		private HatchStyle style;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="CrosshatchAutoInputProtectionFilterProvider" /> class.
		/// </summary>
		public CrosshatchAutoInputProtectionFilterProvider()
		{
			colors = new List<Color>(8);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="CrosshatchAutoInputProtectionFilterProvider" /> class.
		/// </summary>
		/// <param name="colors">A parameter array of <see cref="Color">Colors</see> to be sampled at random when 
		/// drawing the crosshatch.</param>
		public CrosshatchAutoInputProtectionFilterProvider(params Color[] colors)
		{
			if (colors != null && colors.Length > 0)
				this.colors = new List<Color>(colors);
			else
				this.colors = new List<Color>(8);
		}
		#endregion

		#region Methods
		/// <summary>
		/// Loads the provider's settings from the specified <paramref name="config"/>.
		/// </summary>
		/// <exception cref="ProviderException">A value cannot be converted into the required type, or values exist that are not supported by the provider.</exception>
		/// <param name="name">Name of the provider.</param>
		/// <param name="config">A collection of name and value pairs that will be used to initialize the provider.</param>
		public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
		{
			if (config == null)
				throw new ArgumentNullException("config");

			if (String.IsNullOrEmpty(name))
				name = "CrosshatchAutoInputProtectionFilterProvider";

			if (string.IsNullOrEmpty(config["description"]))
			{
				config.Remove("description");
				config.Add("description", "Crosshatch Auto-Input Protection Filter Provider");
			}

			base.Initialize(name, config);

			ProviderHelper helper = new ProviderHelper(config);

			opacity = helper.ParseSingle("opacity", false, .15F);
			randomStyle = helper.ParseBoolean("randomStyle", false, true);
			style = helper.ParseEnum("style", false, HatchStyle.DiagonalCross);
			colors.AddRange(helper.ParseCollection<Color>("colors", false, true, false, ','));
			
			if (colors.Count == 0)
				colors.Add(Color.Black);

			// Throw an exception if unrecognized attributes remain
			if (config.Count > 0)
			{
				string attr = config.GetKey(0);

				if (!String.IsNullOrEmpty(attr))
					throw new ProviderException(string.Format(System.Globalization.CultureInfo.CurrentUICulture,
						Resources.Errors.UnrecognizedAttribute, attr));
			}
		}

		/// <summary>
		/// This method is not supported.
		/// </summary>
		/// <remarks>
		/// <see cref="NotSupportedException"/> is thrown when this method is invoked.
		/// </remarks>
		/// <seealso cref="CanPreProcess"/>
		/// <param name="graphics"><see cref="Graphics" /> object that is the surface on which to draw.</param>
		/// <param name="imageSize">The dimensions of the image.</param>
		public override void PreProcess(Graphics graphics, Size imageSize)
		{
			throw new NotSupportedException();
		}

		/// <summary>
		/// Applies the filter to the specified <paramref name="graphics" /> object after the text is rendered.
		/// </summary>
		/// <remarks>
		/// <para>
		/// When this method is invoked by AIP, the specified <paramref name="graphics"/> object already has random text, a background image and the results 
		/// from all of the preprocessing filters rendered.  The results from all of the post-processing filters that have executed before this one 
		/// will already be rendered as well.
		/// </para>
		/// <para>
		/// A random <see cref="Color"/> is sampled from the list of <see cref="Colors"/> and, if <see cref="RandomStyle"/> is <see langword="true"/>, a 
		/// random <see cref="HatchStyle"/> is selected.  Then a <see cref="HatchBrush"/> is used to draw a crosshatch over the image in the 
		/// current style, color, and <see cref="Opacity"/>.
		/// </para>
		/// <para>
		/// See <see cref="CrosshatchAutoInputProtectionFilterProvider"/> for examples.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">The specified <paramref name="graphics"/> argument is <see langword="null"/>.</exception>
		/// <exception cref="InvalidOperationException">The <see cref="Colors"/> list is empty.</exception>
		/// <seealso cref="CanPostProcess"/>
		/// <param name="graphics"><see cref="Graphics" /> object that is the surface on which to draw.</param>
		/// <param name="imageSize">The dimensions of the image.</param>
		public override void PostProcess(Graphics graphics, Size imageSize)
		{
			if (graphics == null)
				throw new ArgumentNullException("graphics");

			if (colors.Count == 0)
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentUICulture, 
					Resources.Errors.NoColorsForFilter, Name));

			Random rnd = new Random();

			Color color = colors[rnd.Next(0, colors.Count)];

			int op = (int) (opacity * 255);

			if (randomStyle)
			{
				int value;
				do
				{
					value = rnd.Next(0, 53);
				}
				while (!Enum.IsDefined(typeof(HatchStyle), value));

				style = (HatchStyle) value;
			}

			using (HatchBrush brush = new HatchBrush(style, Color.FromArgb(Math.Min(op, 255), color.R, color.G, color.B), Color.Transparent))
			{
				graphics.FillRectangle(brush, 0, 0, imageSize.Width, imageSize.Height);
			}
		}
		#endregion
	}
}